@scrider/formatter 1.3.3 → 1.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -77,6 +77,23 @@ deltaToHtml(delta, { registry }) // Delta → HTML string
77
77
  htmlToDelta(html, { registry }) // HTML string → Delta
78
78
  ```
79
79
 
80
+ **Simple Table presentation (v1.3.4+)** — inline borders/shades for clipboard and Office paste (Delta structure unchanged):
81
+
82
+ ```typescript
83
+ import { deltaToHtml, type TablePresentation } from '@scrider/formatter';
84
+
85
+ const tablePresentation: TablePresentation = {
86
+ grid: true,
87
+ borderColor: '#e7e7e7',
88
+ headerBold: true,
89
+ headerCenter: true,
90
+ };
91
+
92
+ deltaToHtml(delta, { tablePresentation });
93
+ ```
94
+
95
+ See scrider-editor `docs/simple-tables.md` §8 for the full contract (`grid` / `line`, `headerShade`, `zebraRows`, `defaultCellAlign`, …).
96
+
80
97
  ### Markdown Conversion
81
98
 
82
99
  ```typescript
package/dist/index.cjs CHANGED
@@ -61,6 +61,7 @@ __export(index_exports, {
61
61
  deltaToHtml: () => deltaToHtml,
62
62
  deltaToMarkdown: () => deltaToMarkdown,
63
63
  dividerFormat: () => dividerFormat,
64
+ documentPresentationStyleParts: () => documentPresentationStyleParts,
64
65
  escapeHtml: () => escapeHtml,
65
66
  extractBoxOpAttributes: () => extractBoxOpAttributes,
66
67
  extractTableRegion: () => extractTableRegion,
@@ -82,6 +83,7 @@ __export(index_exports, {
82
83
  isTextNode: () => isTextNode,
83
84
  isValidColor: () => isValidColor,
84
85
  isValidHexColor: () => isValidHexColor,
86
+ isZebraBodyRow: () => isZebraBodyRow,
85
87
  italicFormat: () => italicFormat,
86
88
  kbdFormat: () => kbdFormat,
87
89
  linkFormat: () => linkFormat,
@@ -92,6 +94,8 @@ __export(index_exports, {
92
94
  nodeAdapter: () => nodeAdapter,
93
95
  normalizeDelta: () => normalizeDelta,
94
96
  preloadRemark: () => preloadRemark,
97
+ resolveDocumentPresentation: () => resolveDocumentPresentation,
98
+ resolveTablePresentation: () => resolveTablePresentation,
95
99
  sanitizeDelta: () => sanitizeDelta,
96
100
  sizeFormat: () => sizeFormat,
97
101
  slugify: () => slugify,
@@ -2599,6 +2603,94 @@ function slugifyWithDedup(text, usedSlugs) {
2599
2603
  return `${base}-${count}`;
2600
2604
  }
2601
2605
 
2606
+ // src/conversion/html/document-presentation.ts
2607
+ function resolveDocumentPresentation(presentation) {
2608
+ if (!presentation) return void 0;
2609
+ const lineSpacing = typeof presentation.lineSpacing === "number" && presentation.lineSpacing > 0 ? presentation.lineSpacing : void 0;
2610
+ const textIndentCm = typeof presentation.textIndentCm === "number" && presentation.textIndentCm > 0 ? presentation.textIndentCm : void 0;
2611
+ if (lineSpacing === void 0 && textIndentCm === void 0) return void 0;
2612
+ return { lineSpacing, textIndentCm };
2613
+ }
2614
+ var LINE_HEIGHT_TAGS = /* @__PURE__ */ new Set(["p", "li", "blockquote"]);
2615
+ function documentPresentationStyleParts(tag, resolved) {
2616
+ if (!resolved) return [];
2617
+ const parts = [];
2618
+ if (resolved.lineSpacing !== void 0 && LINE_HEIGHT_TAGS.has(tag)) {
2619
+ const pct = Math.round(resolved.lineSpacing * 100);
2620
+ parts.push(`line-height:${resolved.lineSpacing}`);
2621
+ parts.push(`mso-line-height-alt:${pct}%`);
2622
+ }
2623
+ if (resolved.textIndentCm !== void 0 && tag === "p") {
2624
+ parts.push(`text-indent:${resolved.textIndentCm}cm`);
2625
+ }
2626
+ return parts;
2627
+ }
2628
+ function joinStyleParts(parts) {
2629
+ return parts.length > 0 ? ` style="${parts.join("; ")}"` : "";
2630
+ }
2631
+
2632
+ // src/conversion/html/table-presentation.ts
2633
+ var DEFAULT_BORDER_COLOR = "#e7e7e7";
2634
+ var DEFAULT_HEADER_BG = "#f5f5f5";
2635
+ var DEFAULT_ZEBRA_BG = "#fafafa";
2636
+ var CELL_PADDING = "6px 13px";
2637
+ function resolveTablePresentation(presentation) {
2638
+ return {
2639
+ grid: presentation?.grid === true,
2640
+ line: presentation?.line === true && presentation?.grid !== true,
2641
+ borderColor: presentation?.borderColor ?? DEFAULT_BORDER_COLOR,
2642
+ headerShade: presentation?.headerShade === true,
2643
+ zebraRows: presentation?.zebraRows === true,
2644
+ headerBold: presentation?.headerBold === true,
2645
+ headerCenter: presentation?.headerCenter === true,
2646
+ defaultCellAlign: presentation?.defaultCellAlign ?? "left"
2647
+ };
2648
+ }
2649
+ function isZebraBodyRow(headerRowCount, bodyRowIndex) {
2650
+ return (headerRowCount + bodyRowIndex + 1) % 2 === 0;
2651
+ }
2652
+ function isTableCellAlign(value) {
2653
+ return value === "left" || value === "center" || value === "right";
2654
+ }
2655
+ function tableOpenTag(presentation) {
2656
+ if (!presentation.grid && !presentation.line) {
2657
+ return "<table>";
2658
+ }
2659
+ return `<table style="border-collapse: collapse">`;
2660
+ }
2661
+ function buildTableCellStyleAttr(params) {
2662
+ const { presentation, cellTag, colAlign, headerRowCount, bodyRowIndex } = params;
2663
+ const parts = [];
2664
+ parts.push(`padding: ${CELL_PADDING}`);
2665
+ const color = presentation.borderColor;
2666
+ if (presentation.grid) {
2667
+ parts.push(`border: 1px solid ${color}`);
2668
+ } else if (presentation.line) {
2669
+ const width = cellTag === "th" ? "1px" : "0.5px";
2670
+ parts.push(`border-bottom: ${width} solid ${color}`);
2671
+ }
2672
+ let textAlign;
2673
+ if (cellTag === "th" && presentation.headerCenter) {
2674
+ textAlign = "center";
2675
+ } else if (isTableCellAlign(colAlign)) {
2676
+ textAlign = colAlign;
2677
+ } else if (colAlign == null || colAlign === "left") {
2678
+ textAlign = presentation.defaultCellAlign;
2679
+ }
2680
+ if (textAlign && textAlign !== "left") {
2681
+ parts.push(`text-align: ${textAlign}`);
2682
+ }
2683
+ if (cellTag === "th" && presentation.headerBold) {
2684
+ parts.push("font-weight: bold");
2685
+ }
2686
+ if (cellTag === "th" && presentation.headerShade) {
2687
+ parts.push(`background-color: ${DEFAULT_HEADER_BG}`);
2688
+ } else if (cellTag === "td" && presentation.zebraRows && bodyRowIndex !== void 0 && isZebraBodyRow(headerRowCount, bodyRowIndex)) {
2689
+ parts.push(`background-color: ${DEFAULT_ZEBRA_BG}`);
2690
+ }
2691
+ return ` style="${parts.join("; ")}"`;
2692
+ }
2693
+
2602
2694
  // src/conversion/html/delta-to-html.ts
2603
2695
  function deltaToHtml(delta, options = {}) {
2604
2696
  const lines = splitIntoLines(delta);
@@ -2607,6 +2699,7 @@ function deltaToHtml(delta, options = {}) {
2607
2699
  const hierarchicalNumbers = options.hierarchicalNumbers ?? false;
2608
2700
  const blockHandlers = options.blockHandlers;
2609
2701
  const anchorLinks = options.anchorLinks ?? false;
2702
+ const resolvedDocumentPresentation = resolveDocumentPresentation(options.documentPresentation);
2610
2703
  let html = "";
2611
2704
  let listStack = [];
2612
2705
  let counters = [];
@@ -2669,7 +2762,14 @@ function deltaToHtml(delta, options = {}) {
2669
2762
  if (hierarchicalNumbers && listType === "ordered") {
2670
2763
  hierarchicalNumber = counters.slice(0, indent + 1).join(".");
2671
2764
  }
2672
- html += renderListItem(content, listItemAttrs, pretty, indentLevel, hierarchicalNumber);
2765
+ html += renderListItem(
2766
+ content,
2767
+ listItemAttrs,
2768
+ pretty,
2769
+ indentLevel,
2770
+ hierarchicalNumber,
2771
+ resolvedDocumentPresentation
2772
+ );
2673
2773
  } else {
2674
2774
  let headingId;
2675
2775
  if (line.attributes?.header) {
@@ -2681,7 +2781,14 @@ function deltaToHtml(delta, options = {}) {
2681
2781
  headingId = slugifyWithDedup(plainText, slugUsageMap);
2682
2782
  }
2683
2783
  }
2684
- html += renderBlock(content, tag, line.attributes, pretty, headingId);
2784
+ html += renderBlock(
2785
+ content,
2786
+ tag,
2787
+ line.attributes,
2788
+ pretty,
2789
+ headingId,
2790
+ resolvedDocumentPresentation
2791
+ );
2685
2792
  }
2686
2793
  }
2687
2794
  html += closeAllLists(listStack, pretty);
@@ -2782,7 +2889,10 @@ function renderTable(tableLines, embedRenderers, pretty, blockHandlers, options)
2782
2889
  const bodyRows = sortedRows.filter(([, r]) => !r.isHeader);
2783
2890
  const indent = pretty ? " " : "";
2784
2891
  const nl = pretty ? "\n" : "";
2785
- let html = `<table>${nl}`;
2892
+ const usePresentation = options?.tablePresentation !== void 0;
2893
+ const presentation = usePresentation ? resolveTablePresentation(options.tablePresentation) : null;
2894
+ const headerRowCount = headerRows.length;
2895
+ let html = usePresentation && presentation ? `${tableOpenTag(presentation)}${nl}` : `<table>${nl}`;
2786
2896
  if (headerRows.length > 0) {
2787
2897
  html += `${indent}<thead>${nl}`;
2788
2898
  for (const [, row] of headerRows) {
@@ -2794,13 +2904,16 @@ function renderTable(tableLines, embedRenderers, pretty, blockHandlers, options)
2794
2904
  pretty,
2795
2905
  2,
2796
2906
  blockHandlers,
2797
- options
2907
+ options,
2908
+ presentation,
2909
+ headerRowCount
2798
2910
  );
2799
2911
  }
2800
2912
  html += `${indent}</thead>${nl}`;
2801
2913
  }
2802
2914
  if (bodyRows.length > 0) {
2803
2915
  html += `${indent}<tbody>${nl}`;
2916
+ let bodyRowIndex = 0;
2804
2917
  for (const [, row] of bodyRows) {
2805
2918
  html += renderTableRow(
2806
2919
  row.cells,
@@ -2810,8 +2923,12 @@ function renderTable(tableLines, embedRenderers, pretty, blockHandlers, options)
2810
2923
  pretty,
2811
2924
  2,
2812
2925
  blockHandlers,
2813
- options
2926
+ options,
2927
+ presentation,
2928
+ headerRowCount,
2929
+ bodyRowIndex
2814
2930
  );
2931
+ bodyRowIndex += 1;
2815
2932
  }
2816
2933
  html += `${indent}</tbody>${nl}`;
2817
2934
  }
@@ -2819,7 +2936,7 @@ function renderTable(tableLines, embedRenderers, pretty, blockHandlers, options)
2819
2936
  if (pretty) html += "\n";
2820
2937
  return html;
2821
2938
  }
2822
- function renderTableRow(cells, maxCol, cellTag, embedRenderers, pretty, depth, blockHandlers, options) {
2939
+ function renderTableRow(cells, maxCol, cellTag, embedRenderers, pretty, depth, blockHandlers, options, presentation, headerRowCount = 0, bodyRowIndex) {
2823
2940
  const indent = pretty ? " ".repeat(depth) : "";
2824
2941
  const cellIndent = pretty ? " ".repeat(depth + 1) : "";
2825
2942
  const nl = pretty ? "\n" : "";
@@ -2827,8 +2944,19 @@ function renderTableRow(cells, maxCol, cellTag, embedRenderers, pretty, depth, b
2827
2944
  for (let col = 0; col <= maxCol; col++) {
2828
2945
  const cell = cells.get(col);
2829
2946
  const content = cell ? renderLineContent(cell.ops, embedRenderers, blockHandlers, options) : "";
2830
- const alignStyle = cell?.colAlign && cell.colAlign !== "left" ? ` style="text-align: ${cell.colAlign}"` : "";
2831
- html += `${cellIndent}<${cellTag}${alignStyle}>${content}</${cellTag}>${nl}`;
2947
+ let styleAttr = "";
2948
+ if (presentation) {
2949
+ styleAttr = buildTableCellStyleAttr({
2950
+ presentation,
2951
+ cellTag,
2952
+ colAlign: cell?.colAlign,
2953
+ headerRowCount,
2954
+ bodyRowIndex: cellTag === "td" ? bodyRowIndex : void 0
2955
+ });
2956
+ } else {
2957
+ styleAttr = cell?.colAlign && cell.colAlign !== "left" ? ` style="text-align: ${cell.colAlign}"` : "";
2958
+ }
2959
+ html += `${cellIndent}<${cellTag}${styleAttr}>${content}</${cellTag}>${nl}`;
2832
2960
  }
2833
2961
  html += `${indent}</tr>${nl}`;
2834
2962
  return html;
@@ -2952,36 +3080,41 @@ function getListItemAttributes(attributes) {
2952
3080
  }
2953
3081
  return "";
2954
3082
  }
2955
- function renderListItem(content, attrs, pretty, indentLevel, hierarchicalNumber) {
3083
+ function renderListItem(content, attrs, pretty, indentLevel, hierarchicalNumber, resolvedDocumentPresentation) {
2956
3084
  const indent = pretty ? getIndent(indentLevel) : "";
2957
3085
  const innerContent = content || "<br>";
2958
3086
  let fullAttrs = attrs;
2959
3087
  if (hierarchicalNumber) {
2960
3088
  fullAttrs += ` data-number="${hierarchicalNumber}"`;
2961
3089
  }
3090
+ const styleAttr = joinStyleParts(
3091
+ documentPresentationStyleParts("li", resolvedDocumentPresentation)
3092
+ );
3093
+ fullAttrs += styleAttr;
2962
3094
  const html = `${indent}<li${fullAttrs}>${innerContent}</li>`;
2963
3095
  return pretty ? html + "\n" : html;
2964
3096
  }
2965
- function renderBlock(content, tag, attributes, pretty, id) {
3097
+ function renderBlock(content, tag, attributes, pretty, id, resolvedDocumentPresentation) {
2966
3098
  const idAttr = id ? ` id="${escapeHtml(id)}"` : "";
2967
- const styleAttr = getBlockStyleAttribute(attributes);
3099
+ const styleAttr = getBlockStyleAttribute(tag, attributes, resolvedDocumentPresentation);
2968
3100
  const innerContent = content || "<br>";
2969
3101
  const html = `<${tag}${idAttr}${styleAttr}>${innerContent}</${tag}>`;
2970
3102
  return pretty ? html + "\n" : html;
2971
3103
  }
2972
- function getBlockStyleAttribute(attributes) {
2973
- if (!attributes) return "";
2974
- const styles = [];
2975
- const alignVal = attributes.align;
2976
- if (alignVal && typeof alignVal === "string" && alignVal !== "left") {
2977
- styles.push(`text-align: ${alignVal}`);
2978
- }
2979
- if (attributes.indent && typeof attributes.indent === "number") {
2980
- if (!attributes.list) {
2981
- styles.push(`margin-left: ${attributes.indent * 2}em`);
3104
+ function getBlockStyleAttribute(tag, attributes, resolvedDocumentPresentation) {
3105
+ const styles = documentPresentationStyleParts(tag, resolvedDocumentPresentation);
3106
+ if (attributes) {
3107
+ const alignVal = attributes.align;
3108
+ if (alignVal && typeof alignVal === "string" && alignVal !== "left") {
3109
+ styles.push(`text-align: ${alignVal}`);
3110
+ }
3111
+ if (attributes.indent && typeof attributes.indent === "number") {
3112
+ if (!attributes.list) {
3113
+ styles.push(`margin-left: ${attributes.indent * 2}em`);
3114
+ }
2982
3115
  }
2983
3116
  }
2984
- return styles.length > 0 ? ` style="${styles.join("; ")}"` : "";
3117
+ return joinStyleParts(styles);
2985
3118
  }
2986
3119
  function extractPlainText(ops) {
2987
3120
  let text = "";
@@ -5134,6 +5267,7 @@ function extractTableRegion(ops, hintOpIdx) {
5134
5267
  deltaToHtml,
5135
5268
  deltaToMarkdown,
5136
5269
  dividerFormat,
5270
+ documentPresentationStyleParts,
5137
5271
  escapeHtml,
5138
5272
  extractBoxOpAttributes,
5139
5273
  extractTableRegion,
@@ -5155,6 +5289,7 @@ function extractTableRegion(ops, hintOpIdx) {
5155
5289
  isTextNode,
5156
5290
  isValidColor,
5157
5291
  isValidHexColor,
5292
+ isZebraBodyRow,
5158
5293
  italicFormat,
5159
5294
  kbdFormat,
5160
5295
  linkFormat,
@@ -5165,6 +5300,8 @@ function extractTableRegion(ops, hintOpIdx) {
5165
5300
  nodeAdapter,
5166
5301
  normalizeDelta,
5167
5302
  preloadRemark,
5303
+ resolveDocumentPresentation,
5304
+ resolveTablePresentation,
5168
5305
  sanitizeDelta,
5169
5306
  sizeFormat,
5170
5307
  slugify,